﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Disposable.cs" company="MyCoreLib">
//   Copyright (C) 2014 Alexey Evlampiev. All rights reserved.
// </copyright>
// <summary>
//   Common base for disposable classes.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace MyCoreLib
{
    using System;
    using System.Diagnostics;
    using System.Threading;

    /// <summary>
    /// Provides a set of static methods for creating Disposables.
    /// </summary>
    public static class Disposable
    {        

        /// <summary>
        /// Gets the disposable that does nothing when disposed.
        /// </summary>
        public static IDisposable Empty
        {
            get { return DisposableNullObject.Value; }
        }

        /// <summary>
        /// Creates a disposable object that invokes the specified action on disposing
        /// </summary>
        /// <param name="dispose">Action to be executed on the first call to <see cref="IDisposable.Dispose"/>. 
        /// The action is guaranteed to be run at most once.</param>
        /// <returns>New instance of IDisposable that runs the given action on disposing.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="dispose"/> is null.</exception>
        public static IDisposable Create(Action dispose)
        {
            if (dispose == null)
            {
                throw new ArgumentNullException("dispose");
            }

            return new AnonymousDisposable(dispose);
        }                

        #region Nested Types

        /// <summary>
        /// Null Object implementation of <see cref="System.IDisposable"/>.
        /// </summary>
        private sealed class DisposableNullObject : IDisposable
        {
            /// <summary>
            /// Singleton instance of the <see cref="DisposableNullObject"/> class.
            /// </summary>
            public static readonly DisposableNullObject Value = new DisposableNullObject();

            /// <summary>
            /// Prevents a default instance of the <see cref="DisposableNullObject"/> class from being created.
            /// </summary>
            private DisposableNullObject()
            {
            }

            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
            /// </summary>
            void IDisposable.Dispose()
            {
            }

            public override string ToString()
            {
                return this.GetType().Name;
            }
        }

        /// <summary>
        /// Action-based disposable.
        /// </summary>
        private sealed class AnonymousDisposable : IDisposable
        {
            /// <summary>
            /// The dispose handler.
            /// </summary>
            private volatile Action m_dispose;

            /// <summary>
            /// Initializes a new instance of the <see cref="AnonymousDisposable"/> class.
            /// </summary>
            /// <param name="dispose">The dispose handler.</param>
            internal AnonymousDisposable(Action dispose)
            {
                Debug.Assert(dispose != null, "The dispose reference is null");
                m_dispose = dispose;
            }

            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
            /// </summary>
            void IDisposable.Dispose()
            {
#pragma warning disable 0420
                var dispose = Interlocked.Exchange(ref m_dispose, null);
#pragma warning restore 0420
                if (dispose != null)
                {
                    dispose();
                }
            }
        }
        #endregion
    }
}
